/******************************************************************************************************
Copyright (C) 2011 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of Mnova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals Application, CommonSlots, settings, FileDialog, File, Dir, MessageBox, mainWindow, serialization, qsTr, NMRSpectrum,
	NMRProcessing, Peak, Multiplets, LayoutTemplate, MnUi*/
/*jslint plusplus: true, bitwise: true, continue: true,*/

function VerticalScaling_Util() {
	"use strict";


	VerticalScaling_Util.prototype.arrayContains = function (arr, val) {
		//  Returns true if the specified array contains an element that matches a specified value.
		//  @param arr - an Array object to check elements of
		//  @param val - a variable to check against elements of the array */
		var i;
		if (arr === undefined) {
			return false;
		}
		for (i = 0; i < arr.length; i++) {
			if (arr[i] === val) {
				return true;
			}
		}
		return false;
	};
	VerticalScaling_Util.prototype.findSingletsContaining = function (peak, multiplets) {
		//  Return all the multiples of category singlet that contain a given peak.
		//  @param peak - the Peak object to check against each multiplet
		//  @param multiplets - a Multiplets object containing all the Multiplets for a spectrum
		//  @returns an Array object containing Multiplet objects of category singlet, which contain peak
		var i, m, result = [];
		for (i = 0; i < multiplets.count; i++) {
			m = multiplets.at(i);
			if (m.category === "s" && this.arrayContains(m.peaks, peak.id)) {
				result.push(m);
			}
		}
		return result;
	};


	VerticalScaling_Util.prototype.isCarbon = function (spec) {
		//  Returns true if the specified NMRSpectrum object is for a carbon experiment.
		return spec.nucleus() === "13C" || spec.nucleus() === "C13";
	};


	VerticalScaling_Util.prototype.intervalContains = function (interval, val) {

		//  Returns true if a specified interval contains a number.
		//  @param interval - a closed interval range defined by an Array of an upper and lower bound (order independent)
		//  @param val - a Number to check if within interval range
		if (interval === undefined || val === undefined) {
			return false;
		}
		if (interval.length !== 2) {
			throw qsTr("Invalid range: {0} Must be an array of two Numbers").format(interval);
		}
		try {
			if (val >= interval[0] && val <= interval[1]) {
				return true;
			}
			if (val >= interval[1] && val <= interval[0]) {
				return true;
			}
		} catch (err) {
			throw qsTr("Invalid range: {0} Must be an array of two Numbers").format(interval);
		}
		return false;
	};
}


function smartReporting() {
	"use strict";
	var	dialog,
		dirLayoutTemplateKey = "Smart Reporting/Layout Template Directory",
		dir1HProcessingTemplateKey = "Smart Reporting/1H Processing Template Directory",
		dir13CProcessingTemplateKey = "Smart Reporting/13C Processing Template Directory",
		layoutWithProcessingKey = "Smart Reporting/Layout With Processing",
		verticalScalingKey = "Smart Reporting/Vertical Scaling",
		settingsFromLastRunKey = "Smart Reporting/Use settings from last run",
		settingsObjectKey = "Smart Reporting/Settings from last run",
		carbon = 0,
		proton = 0,
		mol = 0,
		txtLayoutTemplate,
		txt1HProcessingTemplate,
		txt13CProcessingTemplate;

	//---------------------Functions--------------------------
	function fileDoesNotExist(aFileName) {

		MessageBox.critical(qsTr("File {0} does not exist.\nModify the path and try again.").format(aFileName), MessageBox.Ok);
	}

	function getItem(type, subtype) {

		var item, i = 0;

		while (i < mainWindow.activeDocument.itemCount(type)) {
			item = mainWindow.activeDocument.item(i, type);
			if (subtype) {
				if (item.subtype === subtype) {
					return item;
				}
				i++;
			} else {
				return item;
			}
		}
	}


	function getSpectra(type, subtype) {


		var item, i = 0,
			itemsArray = [];

		while (i < mainWindow.activeDocument.itemCount(type)) {
			item = mainWindow.activeDocument.item(i, type);
			if (subtype) {
				if (item.subtype === subtype) {
					itemsArray.push(item);
				}
				i++;
			}

		}
		return itemsArray;
	}
	function loadDataIntoTable(aForceRecalculate) {
		var itemsObj, i, items, itemsJSON, rc, row;
		if (dialog.widgets.cbLastRun.checked && !aForceRecalculate) {

			itemsJSON = settings.value(settingsObjectKey, "");
			try {
				itemsObj = JSON.parse(itemsJSON);

				if (itemsObj.length > 0) {
					for (i = 0; i < itemsObj.length; i++) {
						dialog.widgets.twWidths.insertRow(0);
					}

					items = dialog.widgets.twWidths.items;

					for (i = 0; i < items.length; i++) {

						items[i][0].text = itemsObj[i][0];
						items[i][1].text = itemsObj[i][1];
						items[i][2].text = itemsObj[i][2];

						items[i][0].flags =  33;
						items[i][1].flags =  34;
						items[i][2].flags =  34;
					}
					dialog.widgets.twWidths.items = items;
				}

			} catch (ERR) {}

		} else {

			rc = dialog.widgets.twWidths.rowCount;

			for (row = 0; row < rc; row++) {
				dialog.widgets.twWidths.removeRow(0);
			}

			for (i = 0; i < proton; i++) {

				dialog.widgets.twWidths.insertRow(dialog.widgets.twWidths.rowCount);
				items = dialog.widgets.twWidths.items;
				items[dialog.widgets.twWidths.rowCount - 1][0].text = "H " + (1 + i);
				items[dialog.widgets.twWidths.rowCount - 1][0].flags = 33;
				items[dialog.widgets.twWidths.rowCount - 1][1].flags = 34;
				items[dialog.widgets.twWidths.rowCount - 1][2].flags = 34;
				dialog.widgets.twWidths.items = items;
			}
			for (i = 0; i < carbon; i++) {

				dialog.widgets.twWidths.insertRow(dialog.widgets.twWidths.rowCount);
				items = dialog.widgets.twWidths.items;
				items[dialog.widgets.twWidths.rowCount - 1][0].text = "C " + (1 + i);
				items[dialog.widgets.twWidths.rowCount - 1][0].flags = 33;
				items[dialog.widgets.twWidths.rowCount - 1][1].flags = 34;
				items[dialog.widgets.twWidths.rowCount - 1][2].flags = 34;
				dialog.widgets.twWidths.items = items;
			}
		}
	}

	function lastRunChanged() {

		var i, items = dialog.widgets.twWidths.items;

		for (i = 0; i < items.length; i++) {
			dialog.widgets.twWidths.removeRow(0);
		}
		loadDataIntoTable(false);
	}

	function checkInput() {

		var layoutTemplate, file1HprocessingTemplate, file13CprocessingTemplate;

		txtLayoutTemplate = dialog.widgets.leLayoutTemplate.text;
		if (txtLayoutTemplate !== "") {
			layoutTemplate = new File(txtLayoutTemplate);
			if (!layoutTemplate.exists) {
				fileDoesNotExist(txtLayoutTemplate);
				dialog.widgets.leLayoutTemplate.setFocus();
				return false;
			}
		}

		txt1HProcessingTemplate = dialog.widgets.le1HProcessingTemplate.text;
		if (txt1HProcessingTemplate !== "") {
			file1HprocessingTemplate = new File(txt1HProcessingTemplate);
			if (!file1HprocessingTemplate.exists) {
				fileDoesNotExist(txt1HProcessingTemplate);
				dialog.widgets.le1HProcessingTemplate.setFocus();
				return false;
			}
		}

		txt13CProcessingTemplate = dialog.widgets.le13CProcessingTemplate.text;
		if (txt13CProcessingTemplate !== "") {
			file13CprocessingTemplate = new File(txt13CProcessingTemplate);
			if (!file13CprocessingTemplate.exists) {
				fileDoesNotExist(txt13CProcessingTemplate);
				dialog.widgets.le13CProcessingTemplate.setFocus();
				return false;
			}
		}

		if (txtLayoutTemplate === "") {
			MessageBox.information(qsTr("A layout template is needed"));
			return false;
		}

		return true;
	}


	function verticalScaling(spec, specName) {

		var peakTypesOfInterest, customPeaksToIgnore, peaks, multiplets, util, min, max, i, peak, ignore, name,
			singlets, j, snr, answer,
			SINGLET_NUCLIDE_THRESHOLD,
			TALLEST_PEAK_FRACTION_OF_PAGE,
			SNR_THRESHOLD;

		//comment out the peak types that should be ignored when re-scaling the y-axis
		peakTypesOfInterest = [
			Peak.Types.Artifact,
			Peak.Types.Impurity,
			//Peak.Types.Solvent,
			Peak.Types.CS_Ref,
			Peak.Types.Quantification_Ref,
			Peak.Types.Compound
		];

		//add custom chemical shift ranges to ignore when re-scaling the y-axis
		customPeaksToIgnore = {
			'TMS' : [-0.1, 0.1],
			'BOC' : [1.38, 1.42]		//remember: last entry in this list does not end with comma
		};

		//set how many nuclides a singlet can have before it is ignored when re-scaling the y-axis
		SINGLET_NUCLIDE_THRESHOLD = 5;	//singlets with MORE than this number of nuclides will be ignored

		//set the proportion of the page height that the tallest peak (that is not ignored) should occupy
		TALLEST_PEAK_FRACTION_OF_PAGE = 0.7;

		//set how low the signal-to-noise ratio (SNR) can get before we stop scalling any further
		SNR_THRESHOLD = 500;

		//get GSD peaks (assign them if not already present)
		if (spec.peaks() === undefined || !spec.peaks().count) {
			mainWindow.doAction("nmrGSD");
		}
		peaks = spec.peaks();

		//get multiplets if they have been assigned
		multiplets = new Multiplets(spec.multiplets());

		//utility functions encapsulated in class to avoid clashes with other scripts
		util = new VerticalScaling_Util();

		//loop through peaks and find maximum peak, ignoring peaks that are not of interest
		min = 0;
		max = 0;
		for (i = 0; i < peaks.count; i++) {

			peak = peaks.at(i);

			//ignore irelevant peak types
			if (!util.arrayContains(peakTypesOfInterest, peak.type)) {
				continue;
			}

			//ignore custon peaks
			ignore = false;
			for (name in customPeaksToIgnore ) {
				if (customPeaksToIgnore.hasOwnProperty(name)) {
					if (util.intervalContains(customPeaksToIgnore[name], peak.delta())) {
						ignore = true;
						break;
					}
				}
			}
			if (ignore) {
				continue;
			}

			//if a singlet multiplet contains this peak and has more than a set number of nuclides then ignore it
			singlets = util.findSingletsContaining(peak, multiplets);
			for (j = 0; j < singlets.length; j++) {
				if (singlets[j].nH > SINGLET_NUCLIDE_THRESHOLD) {
					ignore = true;
					break;
				}
			}
			if (ignore) {
				continue;
			}


			//if we haven't ignored this peak yet, update min/max values based on this peak
			if (peak.intensity > max) {
				max = peak.intensity;
			} else if (peak.intensity < min) {
				min = peak.intensity;
			}
		}

		//if carbon experiment, extends bounds a bit further
		if (util.isCarbon(spec)) {
			max /= TALLEST_PEAK_FRACTION_OF_PAGE;
			min /= TALLEST_PEAK_FRACTION_OF_PAGE;
		}

		//correct scaling so that signal-to-noise ratio does not get too high
		snr = (max - min) / (2.5 * spec.getNoise());
		if (snr < SNR_THRESHOLD) {
			answer = MessageBox.question(qsTr("Calculated re-scaling for spectrum {0} will result in exceeding the signal-to-noise ratio threshold ({1})!\n\nIf you would like to continue click Yes, if you would like to scale as far as possible without exceeding SNR threshold click no, or click cancel to stop this operation.").format(specName, SNR_THRESHOLD), MessageBox.Yes, MessageBox.No, MessageBox.Cancel);
			if (answer === MessageBox.Cancel) {
				return;
			}
			if (answer === MessageBox.No) {
				//re-calculate max to meet SNR threshold
				max = min + (SNR_THRESHOLD * 2.5 * spec.getNoise());
			}
		}

		//apply new bounds to y-axis
		spec.vertZoom(min, max);

	}


	function fitSpectraNumberToTemplate(spectrumItem, spectraInTemplate) {
		var i, page, newItem;
		if (spectrumItem) {
			for (i = 1; i < spectraInTemplate; i++) {
				page = mainWindow.activeDocument.curPage();
				newItem = page.cloneItems([spectrumItem]);
			}
		}
	}

	function fitMoleculeNumberToTemplate(molItem, moleculesInTemplate) {
		var i, page, tempItem;

		if (molItem) {
			for (i = 1; i < moleculesInTemplate; i++) {
				page = mainWindow.activeDocument.curPage();
				tempItem = page.cloneItems([molItem]);
			}
		}
	}

	function fitSpectrumWidth(spectra, element) {

		var index, specItem, spectrum, items = dialog.widgets.twWidths.items,
			i = 0;

		while (i < items.length) {
			if (items[i][0].text.substring(0, 1) === element) {
				index = parseInt(items[i][0].text.substring(1), 10) - 1;
				if (items[i][1].text !== "" | items[i][2].text !== "") {
					specItem = spectra[index];
					spectrum = new NMRSpectrum(specItem);
					spectrum.horzZoom(items[i][1].text, items[i][2].text);

					if (dialog.widgets.cbVerticalScaling.checked) {
						verticalScaling(spectrum, items[i][0].text);
						if (spectrum.getProperty("peaks.show")) {
							mainWindow.doAction("nmrPeaksShow");
						}
					}

					spectrum.update();
					mainWindow.activeDocument.update();
				}

			}
			i++;
		}
	}

	function getDataFromTable() {
		var i, line, items = dialog.widgets.twWidths.items,
			itemsObj = [];

		for (i = 0; i < items.length; i++) {
			line = [];
			line.push(items[i][0].text);
			line.push(items[i][1].text);
			line.push(items[i][2].text);
			itemsObj.push(line);
		}
		return itemsObj;
	}


	function applyLayoutTemplate(layoutTemplate, applyProcessing) {
		var processingChecked;

		if (layoutTemplate) {

			processingChecked = false;

			if (applyProcessing) {

				if (!mainWindow.getObject("action_Apply_Template_Document_Layout_With_Processing").checked) {
					processingChecked = true;
					mainWindow.doAction("action_Apply_Template_Document_Layout_With_Processing");
				}
			} else {
				if (mainWindow.getObject("action_Apply_Template_Document_Layout_With_Processing").checked) {
					processingChecked = true;
					mainWindow.doAction("action_Apply_Template_Document_Layout_With_Processing");
				}
			}

			mainWindow.activeDocument.applyTemplateDocument(layoutTemplate);

			if (processingChecked) {
				mainWindow.doAction("action_Apply_Template_Document_Layout_With_Processing");
			}
		}
	}

	function applyProcessingTemplate(processingTemplate, specItem) {

		var spectrum = new NMRSpectrum(specItem), proc = new NMRProcessing(processingTemplate);
		spectrum.process(proc);
	}



	function applyTemplates() {
		var protonItem, carbonItem, molItem, oldFileHistory, dataObj, myJSONText, protonSpectra, carbonSpectra;
		try {
			if (mainWindow.activeDocument) {
				protonItem = getItem("NMR Spectrum", "1H");
				carbonItem = getItem("NMR Spectrum", "13C");
				molItem = getItem("Molecule");

				//Processing Templates
				if (protonItem && txt1HProcessingTemplate !== "") {
					applyProcessingTemplate(txt1HProcessingTemplate, protonItem);
				}

				if (carbonItem && txt13CProcessingTemplate !== "") {
					applyProcessingTemplate(txt13CProcessingTemplate, carbonItem);
				}

				//Layout Template
				mainWindow.activeDocument.newPage();
				fitSpectraNumberToTemplate(protonItem, proton);
				fitSpectraNumberToTemplate(carbonItem, carbon);
				fitMoleculeNumberToTemplate(molItem, mol);

				applyLayoutTemplate(txtLayoutTemplate, dialog.widgets.cbApplyProcessing.checked);
				protonSpectra = getSpectra("NMR Spectrum", "1H");
				carbonSpectra = getSpectra("NMR Spectrum", "13C");
				fitSpectrumWidth(protonSpectra, "H");
				fitSpectrumWidth(carbonSpectra, "C");

			} else {
				MessageBox.information(qsTr("A document is needed"));
			}
		} catch (ERR) {
			MessageBox.critical(ERR);
		}

		oldFileHistory = serialization.fileHistory;
		serialization.fileHistory = false;
		serialization.fileHistory = oldFileHistory;

		dataObj = getDataFromTable();
		myJSONText = JSON.stringify(dataObj);

		//Save values in registry
		settings.setValue(dirLayoutTemplateKey, txtLayoutTemplate);
		settings.setValue(dir1HProcessingTemplateKey, txt1HProcessingTemplate);
		settings.setValue(dir13CProcessingTemplateKey, txt13CProcessingTemplate);
		settings.setValue(layoutWithProcessingKey, dialog.widgets.cbApplyProcessing.checked);
		settings.setValue(verticalScalingKey, dialog.widgets.cbVerticalScaling.checked);
		settings.setValue(settingsFromLastRunKey, dialog.widgets.cbLastRun.checked);
		settings.setValue(settingsObjectKey, myJSONText);

	}

	function countItems() {

		var doc, ltCount, i, lt;

		proton = 0;
		carbon = 0;
		mol = 0;

		doc = mainWindow.activeDocument;
		ltCount = doc.itemCount();
		for (i = 0; i < ltCount; i++) {
			lt = new LayoutTemplate(doc.item(i));
			if (lt.isValid()) {
				if (lt.type === "1D") {
					if (lt.subtype === "1H") {
						proton++;
					} else if (lt.subtype === "13C") {
						carbon++;
					}
				} else if (lt.managedName === "Molecule") {
					mol++;
				}
			}
		}
	}

	function reload(forceRecalculate) {

		var layoutTemplate, currentDocument, templateDocument;

		if (dialog.widgets.leLayoutTemplate.text !== "") {
			layoutTemplate = new File(dialog.widgets.leLayoutTemplate.text);
			if (!layoutTemplate.exists || layoutTemplate.name.toUpperCase() === layoutTemplate.absDirPath.toUpperCase()) {
				fileDoesNotExist(dialog.widgets.leLayoutTemplate.text);
				dialog.widgets.leLayoutTemplate.setFocus();
				dialog.hide();
				dialog.show();
				return;
			}
			txtLayoutTemplate  = dialog.widgets.leLayoutTemplate.text;
			currentDocument = mainWindow.activeDocument;
			serialization.open(txtLayoutTemplate);
			templateDocument = mainWindow.activeDocument;
			countItems();
			loadDataIntoTable(forceRecalculate);
			templateDocument.close();
			mainWindow.setActiveWindow(currentDocument);
		} else {
			if (dialog.widgets.leLayoutTemplate.text === "") {
				proton = 0;
				carbon = 0;
				mol = 0;
				loadDataIntoTable(forceRecalculate);
			}
		}
	}
	//Handlers

	function layoutClickedHandler() {
		dialog.widgets.tbLayoutTemplate.clicked.connect(
			function () {
				var fileName, oldFileName;
				oldFileName = dialog.widgets.leLayoutTemplate.text;
				fileName = FileDialog.getOpenFileName("", "Open File", oldFileName.length ? File.absDirPath(oldFileName) : Dir.home());
				if (fileName.length && fileName !== oldFileName) {
					dialog.widgets.leLayoutTemplate.text = fileName;
					reload(false);
				}
			}
		);
	}

	function btLoadClickedHandler() {
		dialog.widgets.btLoad.clicked.connect(
			function () {
				reload(true);
			}
		);
	}

	function dialogAcceptedHandler() {
		dialog.accepted.connect(
			function () {
				if (!checkInput()) {
					smartReporting();
				} else {
					applyTemplates();
				}
			}
		);
	}

	function cbLastRunStateChangedHandler() {
		dialog.widgets.cbLastRun.stateChanged.connect(
			function () {
				lastRunChanged();

			}
		);
	}

	//---------------------Load UI----------------------------

	dialog = Application.loadUiFile('ricares:smartReporting.ui');

	//---------------------Load Settings----------------------

	dialog.widgets.le1HProcessingTemplate.filter = "*.mnp";
	dialog.widgets.le13CProcessingTemplate.filter = "*.mnp";
	dialog.widgets.tb1HProcessingTemplate.clicked.connect(dialog.widgets.le1HProcessingTemplate, CommonSlots.onOpenFile);
	dialog.widgets.tb13CProcessingTemplate.clicked.connect(dialog.widgets.le13CProcessingTemplate, CommonSlots.onOpenFile);

	dialog.widgets.leLayoutTemplate.text = settings.value(dirLayoutTemplateKey, "");
	dialog.widgets.le1HProcessingTemplate.text = settings.value(dir1HProcessingTemplateKey, "");
	dialog.widgets.le13CProcessingTemplate.text = settings.value(dir13CProcessingTemplateKey, "");
	dialog.widgets.cbApplyProcessing.checked = settings.value(layoutWithProcessingKey, false);
	dialog.widgets.cbVerticalScaling.checked = settings.value(verticalScalingKey, false);
	dialog.widgets.cbLastRun.checked = settings.value(settingsFromLastRunKey, false);

	//---------------------Main-------------------------------

	layoutClickedHandler();
	btLoadClickedHandler();
	dialogAcceptedHandler();
	cbLastRunStateChangedHandler();
	reload(false);
	dialog.exec();

}

if (this.MnUi && MnUi.scripts_nmr) {
	MnUi.scripts_nmr.scripts_nmr_SmartReporting = smartReporting;
}
